package cn.swust.designpatternlearningplatform.common.utils;

import cn.swust.designpatternlearningplatform.domain.bo.FileBo;
import cn.swust.designpatternlearningplatform.domain.dto.FileDto;
import com.itextpdf.html2pdf.ConverterProperties;
import com.itextpdf.html2pdf.HtmlConverter;
import com.itextpdf.html2pdf.resolver.font.DefaultFontProvider;
import com.itextpdf.io.font.FontProgram;
import com.itextpdf.io.font.FontProgramFactory;
import com.itextpdf.kernel.geom.PageSize;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.layout.font.FontProvider;
import freemarker.core.Environment;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

@Slf4j
@Component
public class PdfGenerateUtil {

    private final FreeMarkerConfigurer freeMarkerConfigurer;

    private static final String SHIPPED_FONT_RESOURCE_PATH = Objects.requireNonNull(PdfGenerateUtil.class.getResource("/fonts/simsun.ttc")).toString();

    /**
     * 根据properties生成html
     *
     * @param properties
     * @return java.lang.String
     * @author Tang
     * @date 2023/10/15 0:24
     **/
    private String generateHtml(Map<String, Object> properties, String templateName) throws IOException, TemplateException {
        try (StringWriter writer = new StringWriter()) {
            //获取freemarker配置
            Configuration cfg = freeMarkerConfigurer.getConfiguration();
            cfg.setDefaultEncoding(StandardCharsets.UTF_8.name());
            cfg.setURLEscapingCharset("UTF-8");
            cfg.setEncoding(Locale.CHINA, "UTF-8");
            //获取对应的模板
            Template template = cfg.getTemplate(templateName);
            //开始渲染
            template.process(properties, writer);
            //刷新缓冲区
            writer.flush();
            return writer.toString();
        } catch (IOException e) {
            log.error("templateName-{} 缺失", templateName);
            throw new IOException("模板缺失", e);
        } catch (TemplateException e) {
            log.error("properties-{} templateName-{} error-{}", properties, templateName, e.getMessage());
            throw new TemplateException("渲染数据出错", e, Environment.getCurrentEnvironment());
        }
    }

    /**
     * 将html转换成pdf，并转换成字节
     *
     * @param html
     * @param fileBo 生成的pdf的信息
     * @return byte[]
     * @author Tang
     * @date 2023/10/15 0:39
     **/
    public FileDto generatePdf(FileBo fileBo, String html) throws IOException {
        try (ByteArrayOutputStream out = new ByteArrayOutputStream();
             PdfWriter pdfWriter = new PdfWriter(out)) {
            // 生成pdf文档对象
            PdfDocument pdfDocument = new PdfDocument(pdfWriter);
            //设置为A4大小
//            pdfDocument.setDefaultPageSize(PageSize.A4);
            //设置字体
            ConverterProperties properties = new ConverterProperties();
            FontProvider fontProvider = new DefaultFontProvider();
            FontProgram fontProgram = FontProgramFactory.createFont(SHIPPED_FONT_RESOURCE_PATH+",0");
            fontProvider.addFont(fontProgram);
            fontProvider.addStandardPdfFonts();
            properties.setFontProvider(fontProvider);
            //将html转换为pdf
            HtmlConverter.convertToPdf(html, pdfWriter, properties);
            byte[] pdf = out.toByteArray();
            return FileDto.buildPdf(fileBo.getFileName(), pdf, fileBo.getFileParentFolder());
        } catch (IOException e) {
            log.error("缺失字体STSongStd-Light或IO出错");
            throw new IOException("缺失字体或IO出错", e);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 选择对应的模板生成Pdf
     *
     * @param propertiesList 属性列表
     * @param templateName   模板名称
     * @return byte[]
     * @author Tang
     * @date 2023/10/15 0:57
     **/
    public List<FileDto> generatePdf(List<FileBo> propertiesList, String templateName) throws TemplateException, IOException {
        List<FileDto> pdfByte = new ArrayList<>();
        for (FileBo fileBo : propertiesList) {
            Map<String, Object> properties = fileBo.getProperties();
            //生成html
            String html = generateHtml(properties, templateName);
            //生成pdf
            FileDto fileDto = generatePdf(fileBo, html);
            if (fileDto == null) {
                continue;
            }
            pdfByte.add(fileDto);
        }
        //完成并输出
        return pdfByte;
    }

    /**
     * 压缩文件
     *
     * @param outputStream
     * @param fileDtoList
     * @return void
     * @author Tang
     * @date 2023/10/17 20:29
     **/
    public void zipFile(OutputStream outputStream, List<FileDto> fileDtoList) throws IOException {
        if (fileDtoList == null) {
            throw new IOException("文件列表为空");
        }
        try (ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream)) {
            Iterator<FileDto> iterator = fileDtoList.iterator();
            while (iterator.hasNext()) {
                FileDto fileDto = iterator.next();
                byte[] file = fileDto.getFileData();
                //创建文件
                ZipEntry zipEntry;
                if (fileDto.getParentFolder() == null || fileDto.getParentFolder().isEmpty()) {
                    zipEntry = new ZipEntry("默认"
                            + File.separator + fileDto.getFileName() + fileDto.getSuffix());
                } else {
                    zipEntry = new ZipEntry(fileDto.getParentFolder()
                            + File.separator + fileDto.getFileName() + fileDto.getSuffix());
                }
                //设置文件大小
                zipEntry.setSize(file.length);
                //进行压缩
                zipOutputStream.putNextEntry(zipEntry);
                //输出字节流并刷新
                zipOutputStream.write(file);
                zipOutputStream.flush();
                //关闭这个文件，等待输入下一个文件
                zipOutputStream.closeEntry();
                iterator.remove();
            }
            //完成输出
            zipOutputStream.flush();
        } catch (IOException ioException) {
            log.error("{}", ioException.getMessage());
            throw new IOException("压缩失败", ioException);
        }
    }

    public PdfGenerateUtil(FreeMarkerConfigurer freeMarkerConfigurer) {
        this.freeMarkerConfigurer = freeMarkerConfigurer;
    }
}
